package org.sean.common.evaluting;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.InvalidParameterException;
import java.util.*;
import java.util.stream.Stream;

/**
 * 计算表达式求值的上下文基类，
 */
public class NumberEvaluateContextAdaptor<T> implements INumberEvaluateContext {

    private static final Logger LOGGER = LoggerFactory.getLogger(NumberEvaluateContextAdaptor.class);

    private final Map<String, Number> map = new HashMap<>();

    /**
     * 业务对象
     */
    protected final T businessObject;

    /**
     * 构造方法
     * @param businessObject
     */
    public NumberEvaluateContextAdaptor(T businessObject) {
        this.businessObject = businessObject;
        //计算Context
        this.putAnnotatedParametersToContext();
        //获得自定义的参数
        Map<String, Number> customizedParams = this.customizedContextParameters();
        if (customizedParams != null && customizedParams.size() > 0) {
            customizedParams.forEach(this::set);
        }
    }

    private void putAnnotatedParametersToContext() {
        //如果不为空则开始获取所有有注解的属性并获得值
        if (this.businessObject != null) {
            Class<?> clazz = this.businessObject.getClass();
            Class<?>[] inheritedClazz = clazz.getClasses();
            List<Class<?>> allClazz = Arrays.asList(inheritedClazz);
            allClazz.add(clazz);
            allClazz.stream().flatMap(clz -> {
                LOGGER.debug("Start to process the annotated fields in class {}", clz.getName());
                return Stream.of(clz.getDeclaredFields()).filter(f -> f.getAnnotation(ContextParameter.class) != null);
            }).forEach(f -> {
               LOGGER.debug("Start to handle field {} ", f.getName());
               //检查类型，如果不是Number类型则不做任何处理
               if (Number.class.isAssignableFrom(f.getType())) {
                    LOGGER.error("Only number type is supported, but the filed {} has type {}, and it will be ignored", f.getName(), f.getType().getName());
               } else {
                   ContextParameter parameter = f.getAnnotation(ContextParameter.class);
                   if ("".equals(parameter.code().trim())) {
                       LOGGER.warn("Invalid code setting for field {}, it shall be non-empty value", f.getName());
                   } else {
                        if (!f.isAccessible()) {
                            f.setAccessible(true);
                        }
                        Number value = null;
                        try {
                            value = (Number) f.get(businessObject);
                        } catch (Exception e) {
                            LOGGER.error("Fail to get the value of field {}, it will be set as to default value {}", f.getName(), parameter.nullValue());
                            LOGGER.error(e.getMessage(), e);
                        }
                        if (value == null) {
                            LOGGER.warn("The actual value is null or fail to get the value for field {}, then set the value to default value {}", f.getName(), parameter.nullValue());
                            value = parameter.nullValue();
                        }
                        set(parameter.code().trim(), value);
                   }
               }
            });
        }
    }

    /**
     * 构造自定义上下文变量
     * @return
     */
    protected Map<String, Number> customizedContextParameters() {
        return Collections.emptyMap();
    }

    @Override
    public Object get(String name) {
        return this.map.get(name);
    }

    @Override
    public void set(String name, Object value) {
        if (value instanceof Number) {
            this.map.put(name, (Number) value);
        } else {
            LOGGER.error("The target value for {} is not a number.", name);
            throw new InvalidParameterException("Only number type is supported to set into context.");
        }
    }

    @Override
    public boolean has(String name) {
        return this.map.containsKey(name);
    }

    @Override
    public Map<String, Number> getContextMap() {
        return this.map;
    }
}
